<?php
/**
 * properties格式文件解析.
 *
 * properties文件中的注释为单行注释以#标记
 * 同时该文件也允许配置节名称，比如：
 * <code>
 * [test]
 * path=index.php
 * address.zipcode=10000
 * address.show=true //true将会被解析为boolean类型的true，false也将被解析成boolean类型的false
 * </code>
 * 则上述的格式会解析成如下数组:
 * <code>
 * array(
 * 'test' => array(
 * 		'path' => 'index.php',
 * 		'address' => array(
 * 			'zipcode' => '10000',
 * 			'show' => true
 *  	)
 *  )
 *)
 * </code>
 *
 * @author Qian Su <aoxue.1988.su.qian@163.com>
 * @copyright ©2003-2103 phpwind.com
 * @license http://www.windframework.com
 *
 * @version $Id: WindPropertiesParser.php 2973 2011-10-15 19:22:48Z yishuo $
 */
class WindPropertiesParser
{
    const COMMENT = '#';

    const LPROCESS = '[';

    const RPROCESS = ']';

    const ARRAY_SEP = '.';

    public function __construct()
    {
    }

    /**
     * 解析properties文件里的内容.
     *
     * @param string $filename 文件名
     * @param bool   $build    是否按格式解析数据默认为true
     *
     * @return array
     */
    public function parse($filename, $build = true)
    {
        $data = $this->parse_properties_file($filename);

        return $build ? $this->buildData($data) : $data;
    }

    /**
     * 解析properties文件并返回一个多维数组.
     *
     * 载入一个由 filename 指定的 properties 文件，
     * 并将其中的设置作为一个联合数组返回。
     *
     * @param string $filename 文件名
     *
     * @return array
     */
    private function parse_properties_file($filename)
    {
        if (!is_file($filename) || !in_array(substr($filename, strrpos($filename, '.') + 1), array('properties'))) {
            return array();
        }
        $content = explode("\n", WindFile::read($filename));
        $data = array();
        $last_process = $current_process = '';
        foreach ($content as $key => $value) {
            $value = str_replace(array("\n", "\r"), '', trim($value));
            if (0 === strpos(trim($value), self::COMMENT) || in_array(trim($value), array('', "\t", "\n"))) {
                continue;
            }
            $tmp = explode('=', $value, 2);
            if (0 === strpos(trim($value), self::LPROCESS) && (strlen($value) - 1) === strrpos($value, self::RPROCESS)) {
                $current_process = $this->trimChar(trim($value), array(self::LPROCESS, self::RPROCESS));
                $data[$current_process] = array();
                $last_process = $current_process;
                continue;
            }
            $tmp[0] = trim($tmp[0]);
            if (count($tmp) == 1) {
                $last_process ? $data[$last_process][$tmp[0]] = '' : $data[$tmp[0]] = '';
                continue;
            }
            $tmp[1] = trim($tmp[1], '\'"');
            $__tmpValue = strtolower($tmp[1]);
            $tmp[1] = 'false' === $__tmpValue ? false : ('true' === $__tmpValue ? true : $tmp[1]);

            $last_process ? $data[$last_process][$tmp[0]] = $tmp[1] : $data[$tmp[0]] = $tmp[1];
        }

        return $data;
    }

    /**
     * 解析配置数据.
     *
     * @param array $data 源数据
     *
     * @return array
     */
    private function buildData(&$data)
    {
        foreach ((array) $data as $key => $value) {
            if (is_array($value)) {
                $data[$key] = $this->formatDataArray($value);
            } else {
                $this->formatDataFromString($key, $value, $data);
            }
        }

        return $data;
    }

    /**
     * 将每行properties文件转换成数组.
     *
     * @param string $key   properties文件中的键
     * @param string $value properties文件中的值
     * @param array  $data  操作数据,默认为array()
     *
     * @return array
     */
    private function toArray($key, $value, &$data = array())
    {
        if (empty($key) && empty($value)) {
            return array();
        }
        if (strpos($key, self::ARRAY_SEP)) {
            $start = substr($key, 0, strpos($key, self::ARRAY_SEP));
            $end = substr($key, strpos($key, self::ARRAY_SEP) + 1);
            $data[$start] = array();
            $this->toArray($end, $value, $data[$start]);
        } else {
            $data[$key] = $value;
        }

        return $data;
    }

    /**
     * 将原始数组合并成新的数组.
     *
     * @param array $original 原始数组
     * @param array $data     合并后的数组
     *
     * @return array
     */
    private function formatDataArray(&$original, &$data = array())
    {
        foreach ((array) $original as $key => $value) {
            $tmp = $this->toArray($key, $value);
            foreach ($tmp as $tkey => $tValue) {
                if (is_array($tValue)) {
                    if (!isset($data[$tkey])) {
                        $data[$tkey] = array();
                    }
                    $this->formatDataArray($tValue, $data[$tkey]);
                } else {
                    $data[$tkey] = $tValue;
                }
            }
        }

        return $data;
    }

    /**
     * 从字符串中合并数组.
     *
     * @param string $key   待合并的键值
     * @param string $value 待合并的数据
     * @param array  $data  操作数组
     *
     * @return array
     */
    private function formatDataFromString($key, $value, &$data)
    {
        $tmp = $this->toArray($key, $value);
        if (false == strpos($key, self::ARRAY_SEP)) {
            return $tmp;
        }
        $start = substr($key, 0, strpos($key, self::ARRAY_SEP));
        if ((!isset($data[$start]) || !is_array($data[$start])) && isset($tmp[$start])) {
            $data[$start] = $tmp[$start];
            unset($data[$key]);

            return $data;
        }
        foreach ($data as $d_key => $d_value) {
            if (!isset($tmp[$d_key]) || !is_array($tmp[$d_key])) {
                continue;
            }
            foreach ($tmp[$d_key] as $a => $b) {
                $this->merge($a, $b, $data[$start]);
            }
        }
        unset($data[$key]);

        return $data;
    }

    /**
     * 合并格式化的数组.
     *
     * @param string $key   待合并的键值
     * @param mixed  $value 待合并的数据
     * @param array  $data  合并到的数据
     *
     * @return array
     */
    private function merge($key, $value, &$data = array())
    {
        if (!is_array($value)) {
            $data[$key] = $value;

            return $data;
        }

        $v_key = array_keys($value);
        $c_key = $v_key[0];
        if (is_array($value[$c_key])) {
            $this->merge($c_key, $value[$c_key], $data[$key]);
        } else {
            $data[$key][$c_key] = $value[$c_key];
        }

        return $data;
    }

    /**
     * 去除字符串头和尾中指定字符.
     *
     * @param string $str  待处理的数据
     * @param mixed  $char 需要取出的字符
     *
     * @return string 处理后的数据
     */
    private function trimChar($str, $char = ' ')
    {
        $char = is_array($char) ? $char : array($char);
        foreach ($char as $value) {
            $str = trim($str, $value);
        }

        return $str;
    }
}
